CHAPTER 9

Exception Handling

The common language runtime (CLR) contains strong support for exceptions. You can create and throw exceptions at a point where code execution cannot continue due to some excep-tional condition (usually a method failure or invalid state). Once exceptions are thrown, the CLR begins the process of unwinding the call stack iteratively frame by frame. As it does so, it cleans up any objects that are local to each stack frame. At some point, a frame on the stack could have an exception handler registered for the type of exception thrown. Once the CLR reaches that frame, it invokes the exception handler to remedy the situation. If the stack unwind finishes and a handler is not found for the exception thrown, then the unhandled exception event for the current application domain may be fired and the application could be aborted.

Writing exception-safe code is a difficult art to master. It would be a mistake to assume that the only tasks required when writing exception-safe code are simply throwing exceptions when an error occurs and catching them at some point. Instead, exception-safe coding tech-niques are those with which you can guarantee the integrity of the system in the face of exceptions. When an exception is thrown, the runtime will iteratively unwind the stack while cleaning up. Your job as an exception-safe programmer is to structure your code in such a way that the integrity of the state of your objects is not compromised as the stack unwinds. That is the true essence of exception-safe coding techniques.

Handling Exceptions

Where should you handle exceptions? You can find the answer by applying a variant of the Expert pattern, which states that work should be done by the entity that is the expert with respect to that work. That is a circuitous way of saying that you should catch the exception at the point where you can actually handle it with some degree of knowledge available to remedy the situation. Sometimes, the catching entity could be close to the point of the exception gen-eration within the stack frame. The code could catch the exception, then take some corrective action, and then allow the program to continue to execute normally. Other times, the only reasonable place to catch an exception is at the entry-point Main method, at which point you

could either abort the process after providing some useful data, or you could reset the process as if the application were just restarted. The bottom line is that you should figure out the best way to recover from exceptions and where it makes the most sense to do so.

Avoid Using Exceptions to Control Flow

It can be tempting to use exceptions to manage the flow of execution in complex methods. This is generally not a good idea. Exceptions are expensive to generate and handle. Therefore, if you were to use them to control execution flow within a method that is at the heart of your application, your performance will likely degrade. Secondly, it trivializes the nature of excep-tions in the first place. The point is to indicate an exceptional condition in a way that you can handle or report it cleanly.

Programmers can be rather lazy when it comes to handling error conditions. You’ve probably seen code where the programmer didn’t bother to check the return value of an API function or method call. Exceptions provide a syntactically succinct way to indicate and han-dle error conditions without littering your code with a plethora of If…Then blocks and other traditional (nonexception-based) error-handling constructs.

At the same time, the runtime supports exceptions, and it does a lot of work on your behalf when exceptions are thrown. Unwinding the stack is no trivial task in and of itself. Lastly, the point where an exception is thrown and the point where it’s handled can be dis-jointed and have no connection to each other. Thus, it can be difficult when reading code to determine where an exception will get caught and handled. These reasons alone are enough for you to stick to traditional techniques when managing normal execution flow.

Mechanics of Handling Exceptions in VB 2005

If you’ve ever used exceptions in other C-style languages such as C++, Java, or even C/C++ using the Microsoft structured exception-handling extensions (__try/__catch/__finally), then you’re already familiar with the basic syntax of exceptions in Visual Basic (VB). In that case, you may find yourself skimming the next few sections or treating the material as a refresher.

Throwing Exceptions

The act of throwing an exception is actually quite easy. You simply execute a Throw statement where the parameter to the Throwstatement is the exception you would like to throw. For example, suppose you’ve written a custom collection class that allows users to access items by index, and you’d like to notify users when an invalid index is passed as a parameter. You could throw an ArgumentOutOfRange exception, such as in the following code:

Public Class MyCollection
    Private Count As Integer

    Public Function GetItem(ByVal index As Integer) As Object
        If index < 0 OrElse index >= Count Then
            Throw New ArgumentOutOfRangeException()
        End If
    End Function
End Class

The runtime can also throw exceptions as a side effect to code execution. An example of a system-generated exception is NullReferenceException, which occurs if you attempt to access a field or call a method on an object when, in fact, the reference to the object doesn’t exist.